#include <exception>
#include <iostream>
#include "ocl_prog.h"


static cl_program program;
static cl_kernel hKernel;


ocl_prog::ocl_prog()
{}

void ocl_prog::init(const char* fIN, const char* fOUT)
{
	if( programFile.empty())
	{	// dont allow running base class without sub classes
		std::cout<<"Running base class <ocl_prog> directly not allowed\n";
		exit(1);
	}

	cl_int error;
	char tmp[1024] = {0x0};
	int i, j, k;
	int current_row = -2;
	int row_count = 0;
	int M;
	int numTabs = 0;
	int firstTime = 1;
	

	// open files for I/O
	FILE *_in;
	_in = fopen(fIN,"r");
	if(_in == NULL)
	{
		perror("File open error");
		exit(EXIT_FAILURE);
	}

	_out = fopen(fOUT,"w"); 
	// end opening files


	while(fgets(tmp, sizeof(tmp), _in) != 0)
	{
		char ch;
		int i1 = 0;
		ch = tmp[0];
		while(ch != '\0' && firstTime)
		{
			ch = tmp[i1];
			if(ch == '\t')
			{
				numTabs++;
			}
			i1++;
		}
		row_count++;
		firstTime = 0;
	}

	M = numTabs + 1 - 3;
	NUM_ROWS = row_count - 2;

	int size_arr;
	size_arr = M * NUM_ROWS;
	int mem_size_arr;
	mem_size_arr = sizeof(float) * size_arr;

//	float *h_arr = (float*)malloc(mem_size_arr);
	float *h_arr;
	try{
		h_arr = new float[size_arr];
	}catch(std::exception& e)
	{
		std::cout<< e.what() <<"\n";
	}

	fclose(_in);
	_in = fopen(fIN, "r");

	float *blank = (float*)malloc(sizeof(float) * M);

	while(fgets(tmp, sizeof(tmp), _in) != 0)
	{
		char *realPtr;
		int numTabsSkipped;
		if( current_row < 0)
		{
			current_row++;
			continue;
		}
		realPtr = tmp;
		numTabsSkipped = 0;
		while(numTabsSkipped < 3)
		{
			realPtr++;
			if(*realPtr == '\t')
			{
				numTabsSkipped++;
			}
		}
		realPtr++;
		parse(realPtr,"\t", blank, M);  
		for(i = 0; i < M; i++)
		{
			h_arr[current_row * M + i] = blank[i];
		}
		current_row++;
	}

	fclose(_in);

	int size_info; 
	size_info = NUM_ROWS * NUM_ROWS;
	mem_size_info = sizeof(float) * size_info;
//	float *h_info = (float*)malloc(mem_size_info);
//	float *answer = (float*)malloc(sizeof(float) * NUM_ROWS * NUM_ROWS);

	float *h_info;
	try{	// try to allocate mem for output data, <result> replaces <float *answer>
		result = new float[size_info];
		h_info = new float[size_info];
	}
	catch(std::exception& e)
	{		// failed to allocate mem
		std::cout<< e.what() <<std::endl;
		exit(1);
	}

	error = oclGetPlatformID(&platform);
	oclCheckError(error, CL_SUCCESS);

	error = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
	oclCheckError(error, CL_SUCCESS);

	context = clCreateContext (0, 1, &device, NULL, NULL, &error);
	oclCheckError(error, CL_SUCCESS);

	hCmdQueue = clCreateCommandQueue(context, device, 0, &error);
	oclCheckError(error, CL_SUCCESS);

	d_arr = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, mem_size_arr, h_arr, &error);
	assert( error == CL_SUCCESS);

	/*d_pdfxy = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_pdfxy, &error);
	assert( error == CL_SUCCESS);

	d_pdfx = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_pdfx, &error);
	assert( error == CL_SUCCESS);

	d_pdfy = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_pdfy, &error);
	assert( error == CL_SUCCESS);

	d_z = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_z, &error);
	assert( error == CL_SUCCESS);

	d_norm = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_norm, &error);
	assert( error == CL_SUCCESS);

	d_xz = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_xz, &error);
	assert( error == CL_SUCCESS);

	d_yz = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_yz, &error);
	assert( error == CL_SUCCESS);

	d_normxy = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, array_size, h_normxy, &error);
	assert( error == CL_SUCCESS);*/

	d_info = clCreateBuffer(context, CL_MEM_READ_WRITE, mem_size_info, NULL, &error);
	assert( error == CL_SUCCESS);
		
	initMutInfoGPU(context, device);
	MutInfoGPU(hCmdQueue, d_info, d_arr, M, NUM_ROWS);

	delete[] h_info;
	delete[] h_arr;
}


void ocl_prog::run()
{
	cl_int error;
	error = clEnqueueReadBuffer(hCmdQueue, d_info, CL_TRUE, 0, mem_size_info, result, 0, NULL, NULL);
	assert( error == CL_SUCCESS);

	 
	for(int j = 0; j < NUM_ROWS; j++)
	{
		for(int k = 0; k < NUM_ROWS; k++)
		{
			fprintf(_out, "Row%d vs. Row%d %f  \t",j , k, result[j * NUM_ROWS + k]);
		}
		fprintf(_out, "\n");
	}


}

void ocl_prog::end()
{
	closeMutInfoGPU();
	error = clReleaseMemObject(d_info);
	error |= clReleaseMemObject(d_arr);
	error |= clReleaseCommandQueue(hCmdQueue);
	error |= clReleaseContext(context);
	assert( error == CL_SUCCESS);


//	free(answer);
	delete[] result;

	fclose(_out);
	//shrExit(argc, argv);

}


void ocl_prog::initMutInfoGPU(cl_context in_context, cl_device_id in_device)
{
	cl_int error;
	size_t src_size;
	const char *path = shrFindFilePath( programFile, NULL);
	char *source = oclLoadProgSource( path, "", &src_size);
	shrCheckError(source != NULL, shrTRUE);
	program = clCreateProgramWithSource(in_context, 1, (const char **)&source, &src_size, &error);
	assert(error == CL_SUCCESS);

	error = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
	assert(error == CL_SUCCESS);

	char *build_log;
	size_t log_size;

	clGetProgramBuildInfo(program, in_device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
	build_log = new char[log_size+1];

	clGetProgramBuildInfo(program, in_device, CL_PROGRAM_BUILD_LOG, log_size, build_log, NULL);
	build_log[log_size] = '\0';
	printf("%s\n", build_log);
	free(build_log);

	hKernel = clCreateKernel(program, "MutualInfo", &error);
	assert(error == CL_SUCCESS);
	free(source);
}

void ocl_prog::closeMutInfoGPU(void)
{
    cl_int error;
    error  = clReleaseKernel(hKernel);
    error |= clReleaseProgram(program);
    shrCheckError(error, CL_SUCCESS);
}

void ocl_prog::MutInfoGPU(cl_command_queue hCmdQueue, cl_mem in_info, cl_mem in_arr, cl_int in_M, cl_int in_NUM_ROWS)
{
	cl_int error;
	size_t localWorkSize = in_NUM_ROWS;
	size_t globalWorkSize = in_NUM_ROWS;

	error = clSetKernelArg(hKernel, 0, sizeof(cl_mem), (void *)&in_info);
	assert(error == CL_SUCCESS); 
	error |= clSetKernelArg(hKernel, 1, sizeof(cl_mem), (void *)&in_arr);
	assert(error == CL_SUCCESS);
	error |= clSetKernelArg(hKernel, 2, sizeof(cl_int), (void *)&in_M);
	assert(error == CL_SUCCESS);
	error |= clSetKernelArg(hKernel, 3, sizeof(cl_int), (void *)&in_NUM_ROWS);
	assert(error == CL_SUCCESS);

	//clGetKernelWorkGroupInfo(hKernel, NULL, CL_KERNEL_WORK_GROUP_SIZE, sizeof(size_t), &localWorkSize, NULL);
	//printf("\n CL_KERNEL_WORK_GROUP_SIZE: %i\n" , localWorkSize); 

	error = clEnqueueNDRangeKernel(hCmdQueue, hKernel, 1, NULL, &globalWorkSize, &localWorkSize, 0, NULL, NULL);
	assert(error == CL_SUCCESS);
}

void ocl_prog::parse( char *line_record, char *delim, float data_array[], int num)
{
	int i;
    char *pPosition = strtok(line_record, delim);
	float data;

    for (i = 0; i < num; i++)
    {
		data = strtod(pPosition, NULL);
		data_array[i] = data;
		pPosition = strtok('\0', delim);
	}		
}